using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace Kopain
{

    /// <summary>This class represents the treeview used for navigation.
    /// </summary>
    internal class NotesTreeview : System.Windows.Forms.TreeView
    {

#region Private properties.

        /// <summary>This is a placeholder for the last selected Node.
        /// To Start with it is set to null since no Node was selected earlier.
        /// </summary>
        private TreeNode _previousSelectedNode = null;

#endregion  //Private properties.

#region Public properties.

        /// <summary>This enum represents the different directions to move.
        /// </summary>
        internal enum MoveDirection
        {
            Up, 
            Down, 
            Left, 
            Right
        }

#endregion  //Public properties.

#region Events and Delegates.

    /// <summary>This Delegate is for an event that is fired before the Node is changed.
    /// </summary>
    /// <param name="previousNode"></param>
    /// <param name="eventArgsNextNode"></param>
    internal delegate void BeforeBeforeSelectDelegate( TreeNode previousNode, TreeViewCancelEventArgs eventArgsNextNode );

    /// <summary>This event is fired when the selected Node is changed, right before it changes.
    /// This way the control container can do stuff, verify the node change and prepare the next node.
    /// </summary>
    internal event BeforeBeforeSelectDelegate OnBeforeBeforeSelect;
    
#endregion  //Events and Delegates.

#region Message sinks.

        /// <summary>This method is called by the framework and then it raises an event with previous and next Nodes as parameters.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnBeforeSelect( TreeViewCancelEventArgs e )
        {
            if( null == _previousSelectedNode )
            {
                Raise_OnBeforeBeforeSelect( null, e );
            }
            else
            {
                Raise_OnBeforeBeforeSelect( _previousSelectedNode, e );
            }
            _previousSelectedNode = e.Node;
        }

        protected override void OnKeyDown( KeyEventArgs e )
        {
            base.OnKeyDown( e );

            //  There is no need to check for these keys since they are trapped by the menu and the same stuff is done.
            //if( Common.CheckKeys( e.KeyData, Keys.Alt | Keys.Shift | Keys.Up, true ) )
            //{
            //    MoveActiveNode( MoveDirection.Up );
            //}
            //else if( Common.CheckKeys( e.KeyData, Keys.Alt | Keys.Shift | Keys.Down, true ) )
            //{
            //    MoveActiveNode( MoveDirection.Down );
            //}
            //else if( Common.CheckKeys( e.KeyData, Keys.Alt | Keys.Shift | Keys.Left, true ) )
            //{
            //    MoveActiveNode( MoveDirection.Left );
            //}
            //else if( Common.CheckKeys( e.KeyData, Keys.Alt | Keys.Shift | Keys.Right, true ) )
            //{
            //    MoveActiveNode( MoveDirection.Right );
            //}
            //else if( Common.CheckKeys( e.KeyData, Keys.F2, false ) )
            //{
            //    SelectedNode.BeginEdit();
            //}
        }

#endregion  //Message sinks.

#region Public methods.

        /// <summary>This method adds a child Note to the currently active/selected Note/Node.  It returns the newly created node.
        /// </summary>
        /// <param name="noteRoot"></param>\
        /// <returns></returns>
        internal TreeNode AddChildToActiveNode(NoteRoot noteRoot)
        {
            NoteType newNote = null;
            TreeNode newNode = null;

            if (null == SelectedNode)
            {
                //  If there is no Note in the tree we have no selected Node.  So create a Note directly under the root.
                newNote = noteRoot.AddChild();
                newNode = Nodes.Add(newNote.id, newNote.title);
            }
            else
            {
                //  Get the selected Note and add a child Note to it.
                NoteType selectedNote = GetNote(SelectedNode);
                newNote = selectedNote.AddChild();
                newNode = SelectedNode.Nodes.Add(newNote.id, newNote.title);
            }
            //  Update the treeview with the data of the new Note.
            newNode.Tag = newNote;
            return newNode;
        }

        /// <summary>This method adds a sibling Note to the currently active/selected Note/Node.  It returns the newly created Node.
        /// </summary>
        /// <param name="noteRoot"></param>
        /// <returns></returns>
        internal TreeNode AddSiblingToActiveNode(NoteRoot noteRoot)
        {
            NoteType newNote = null;
            TreeNode newNode = null;

            if (null == SelectedNode)
            {
                //  If there is no Note in the tree we have no selected Node.  So create a Note directly under the root.
                newNote = noteRoot.AddChild();
                newNode = Nodes.Add(newNote.id, newNote.title);
            }
            else
            {
                //  Get the selected Note and add a sibling to it.
                NoteType selectedNote = GetNote(SelectedNode);
                newNote = selectedNote.AddSibling();
                if (IsTopmost(SelectedNode))
                {
                    //  Topmost.  Add directly under the root.
                    newNode = Nodes.Insert(SelectedNode.Index + 1, newNote.id, newNote.title);
                }
                else
                {
                    newNode = SelectedNode.Parent.Nodes.Insert(SelectedNode.Index + 1, newNote.id, newNote.title);
                }
            }
            //  Update the treeview with the data of the new Note.
            newNode.Tag = newNote;
            return newNode;
        }

        /// <summary>This method clears the tree.
        /// It does not do anything else like saving a file or so.
        /// </summary>
        internal void ClearTree()
        {
            this.Nodes.Clear();
        }

        /// <summary>This method takes alite of Notes and finds out their tree structure and builds a tree out of it.
        /// </summary>
        /// <param name="notes"></param>
        internal void ConstructTree( NoteType[] notes )
        {
            ConstructSubTree( this.Nodes, notes );
        }

        /// <summary>This method deletes the active Note.
        /// </summary>
        internal void DeleteActiveNode(NoteRoot noteRoot)
        {
            //  If there isn't any node selected we cannot delete it either.
            if (null == SelectedNode) { return; }

            NoteType selectedNote = GetNote(SelectedNode);
            selectedNote.Delete();

            SelectedNode.Remove();
        }

        /// <summary>This method returns the next visible Node.
        /// </summary>
        /// <returns></returns>
        internal TreeNode GetNextVisibleNode()
        {
            //  Get all Nodes in a depth first order.
            List<TreeNode> nodeList = GetAllNodesDepthFirst();

            //  Find the user-selected one.
            //

            //  Loop to find the index of the selected Node.
            int selectedIndex = 0;
            for (int i = 0; i < nodeList.Count; ++i)
            {
                if (this.SelectedNode == nodeList[i])
                {
                    selectedIndex = i;
                    //TODO: Leave the loop directly.
                }
            }

            //  Loop through the following Nodes to find the first visible.
            if (selectedIndex < nodeList.Count)
            {
                for (int i = selectedIndex + 1; i < nodeList.Count; ++i)
                {
                    if (nodeList[i].IsVisible)
                    {
                        return nodeList[i];
                    }
                }
            }

            //  If we come here there is no following Node so return the selected.
            return SelectedNode;
        }

        /// <summary>This method returns a Node by its Id.  If no Node is found null is returned.
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        internal TreeNode GetNodeById(string guid)
        {
            foreach (TreeNode node in this.GetAllNodesDepthFirst())
            {
                if (guid == GetNote(node).id)
                {
                    return node;
                }
            }
            return null;
        }

        /// <summary>This method returns the Note behind the Node.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        internal static NoteType GetNote( TreeNode node )
        {
            return (NoteType)node.Tag;
        }

        /// <summary>This method returns the previous visible Node.
        /// </summary>
        /// <returns></returns>
        internal TreeNode GetPreviousVisibleNode()
        {
            //  Get all Nodes in a depth first order.
            List<TreeNode> nodeList = GetAllNodesDepthFirst();

            //  Loop to find the index of the selected Node.
            int selectedIndex = 0;
            for( int i = 0 ; i < nodeList.Count ; ++i )
            {
                if( this.SelectedNode == nodeList[i] )
                {
                    selectedIndex = i;
                    //TODO: Leave the loop directly.
                }
            }

            //  Loop through the Nodes again but backwards to find the previous visible.
            if( selectedIndex >= 1)
            {
                for( int i = selectedIndex -1 ; i >=0 ; --i )
                {
                    if( nodeList[i].IsVisible )
                    {
                        return nodeList[i];
                    }
                }
            }

            //  If we come here there is no following Node so return the selected.
            return SelectedNode;
        }

        /// <summary>This method returns the Note of the selected node.
        /// Note the difference between Note and Node!
        /// </summary>
        /// <returns></returns>
        internal NoteType GetSelectedNote()
        {
            return GetNote(SelectedNode);
        }

        /// <summary>This method returns the siblings of a node.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        internal TreeNodeCollection GetSiblings(TreeNode node)
        {
            if (IsTopmost(node))
            {
                return Nodes;
            }
            else
            {
                return node.Parent.Nodes;
            }
        }

        /// <summary>This method returns a list of all Notes and if they are visible or not in the treeview.
        /// </summary>
        /// <returns>A tuple list where the first is a boolean if the Note/node is visible or not and the last
        /// contains the Note itself.</returns>
        internal List<LIUS.Templates.Tuple.Tuple<bool, NoteType>> GetVisibleStatusOfNodes()
        {
            List<LIUS.Templates.Tuple.Tuple<bool, NoteType>> ret = new List<LIUS.Templates.Tuple.Tuple<bool, NoteType>>();
            foreach (TreeNode node in GetAllNodesDepthFirst())
            {
                ret.Add(new LIUS.Templates.Tuple.Tuple<bool, NoteType>(node.IsVisible, GetNote(node)));
            }
            return ret;
        }

        /// <summary>This method returns true if it is the first among it's siblings.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        internal bool IsFirstSibling(TreeNode node)
        {
            return 0 == node.Index;
        }

        /// <summary>This method returns if it is the last among it's siblings.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        internal bool IsLastSibling(TreeNode node)
        {
            return node.Index == GetSiblings(node).Count - 1;
        }

        /// <summary>This method returns true if the Node is directly under the root.
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        internal bool IsTopmost(TreeNode node)
        {
            return (null == node.Parent);
        }

        /// <summary>This method moves the selected node in the tree.
        /// </summary>
        /// <param name="direction"></param>
        internal void MoveActiveNode( MoveDirection direction )
        {
            //  If there isn't any node selected we cannot move it either.
            if( null == SelectedNode ) { return; }

            MoveNode( SelectedNode, direction );
        }

        /// <summary>This method moves a node in the tree.
        /// </summary>
        /// <param name="node"></param>
        /// <param name="direction"></param>
        internal void MoveNode( TreeNode node, MoveDirection direction )
        {
            //  If there is only one node then we cannot move it either.
            if( (1 == Nodes.Count) && (0 == Nodes[0].Nodes.Count) ){   return;  }

            ////  Store the selected node to have a handle to what we move about.
            //TreeNode node = SelectedNode;
            SelectedNode = null;

            switch( direction )
            {
                //  Move the node to a lower index among it's siblings.
                case MoveDirection.Up:
                    {
                        // We cannot move the upmost node any more up.
                        if( IsFirstSibling( node ) ) { return; }

                        int index = node.Index;
                        TreeNodeCollection targetSiblings = GetSiblings(node);

                        //  Move the node.
                        node.Remove();
                        targetSiblings.Insert( index - 1, node );
                    }
                    break;

                //  Move the node to a higher index.
                case MoveDirection.Down:
                    {
                        //  We cannot move the dowmost node any more down.
                        if( IsLastSibling( node ) ) { return; }

                        int index = node.Index;
                        TreeNodeCollection targetSiblings = GetSiblings( node );

                        //  Move the node.
                        node.Remove();
                        targetSiblings.Insert( index + 1, node );
                    }
                    break;

                case MoveDirection.Left:
                    {
                        //  A topmost node cannot be moved higher in the hierarchy.
                        if( IsTopmost( node ) ){ return; }
                    
                        TreeNodeCollection targetSiblings =
                            ( null == node.Parent.Parent ) ? Nodes : node.Parent.Parent.Nodes;
                        int parentIndex = node.Parent.Index;

                        //  Move the node.
                        node.Remove();
                        targetSiblings.Insert( parentIndex + 1, node );
                    }
                    break;

                case MoveDirection.Right:
                    {
                        //  If it is the upmost/first node among it's siblings there is no node to act as parent.  Then we cannot move it to the right.
                        if( IsFirstSibling(node) ) { return; }

                        //  Retrieve the node above to become parent.
                        TreeNode targetParent = GetSiblings( node )[node.Index - 1];

                        //  Move the node.
                        node.Remove();
                        targetParent.Nodes.Add( node );
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException( "direction", direction, "The direction is not implemented." );
            }

            //  Move the note.
            GetNote( node ).Move( TranslateToNoteTypeDirection( direction ) );

            SelectedNode = node;
        }

        /// <summary>This method sets the active node to being able to be changed name of.
        /// </summary>
        internal void SetModeToChangeName()
        {
            SelectedNode.BeginEdit();
        }

        /// <summary>This method sets the title of the selected node to the Note's title.
        /// This is normally done when adding Notes but if the user changes the Note's title through a dialogue
        /// the node must be updated.
        /// </summary>
        internal void UpdateSelectedNodeTitle()
        {
            SelectedNode.Text = GetNote( SelectedNode ).title;
        }

#endregion  //Public methods.

#region Private methods.

        /// <summary>This method loops through the subtree and adds a node per each.
        /// </summary>
        /// <param name="collNodes"></param>
        /// <param name="collNotes"></param>
        private void ConstructSubTree( TreeNodeCollection collNodes, NoteType[] lstNotes )
        {
            //  Loop through the notes and add a treeviewnode for each.
            if( null != lstNotes )
            {
                for( int i = 0 ; i < lstNotes.Length ; i++ )
                {
                    NoteType n = lstNotes[i];
                    TreeNode newNode = collNodes.Add( n.id, n.title );
                    if( n.visible ) { newNode.EnsureVisible(); }
                    newNode.Tag = n;
                    if( null != n.Note )
                    {
                        ConstructSubTree( newNode.Nodes, n.Note );
                    }
                }
            }
        }

        /// <summary>This method converts a TreeNodeCollection which you get from
        /// this.Nodes to a List of nodes.
        /// </summary>
        /// <param name="collNodes"></param>
        /// <returns></returns>
        private List<TreeNode> ConvertToList(TreeNodeCollection collNodes)
        {
            List<TreeNode> ret = new List<TreeNode>();
            foreach (TreeNode node in collNodes)
            {
                ret.Add(node);
            }
            return ret;
        }

        /// <summary>This method returns a list of all the Nodes.
        /// They are added to the array depth first.
        /// </summary>
        /// <returns></returns>
        private List<TreeNode> GetAllNodesDepthFirst()
        {
            List<TreeNode> ret = new List<TreeNode>();
            foreach( TreeNode n in this.Nodes )
            {
                GetAllNodesDepthFirst( ret, n );
            }
            return ret;
        }

        /// <summary>This method returns all nodes as a list, depth first.
        /// </summary>
        /// <param name="nodeList"></param>
        /// <param name="node"></param>
        private void GetAllNodesDepthFirst( List<TreeNode> nodeList, TreeNode node )
        {
            nodeList.Add( node );
            foreach( TreeNode n in node.Nodes )
            {
                GetAllNodesDepthFirst( nodeList, n );
            }
        }

        /// <summary>This method returns all the treeview nodes as a list.
        /// The tree is looped-through depth first.
        /// </summary>
        /// <returns></returns>
        private List<TreeNode> GetNodesDepthFirst()
        {
            return GetNodesDepthFirstRec(null);
        }

        /// <summary>This method is the recursive part of GetNodesDepthFirst.
        /// How I long for pascal's way of storing a method inside a method.
        /// </summary>
        /// <param name="nodes"></param>
        /// <returns></returns>
        private List<TreeNode> GetNodesDepthFirstRec(List<TreeNode> nodes)
        {
            List<TreeNode> ret = new List<TreeNode>();
            if (null == nodes)
            {
                nodes = ConvertToList(Nodes);
            }
            foreach (TreeNode node in nodes)
            {
                ret.Add(node);
                foreach (TreeNode n in GetNodesDepthFirstRec(ConvertToList(node.Nodes)))
                {
                    ret.Add(n);
                }
            }
            return ret;
        }

        /// <summary>This is a helper method to raise an event if there is such
        /// </summary>
        /// <param name="previousNode"></param>
        /// <param name="eventArgsNextNode"></param>
        private void Raise_OnBeforeBeforeSelect( TreeNode previousNode, TreeViewCancelEventArgs eventArgsNextNode )
        {
            BeforeBeforeSelectDelegate tempEvent = OnBeforeBeforeSelect;
            if( null != tempEvent )
            {
                OnBeforeBeforeSelect( previousNode, eventArgsNextNode );
            }
        }

        /// <summary>This method converts a NotesTreeview.Direction to a NoteType.Direction.
        /// </summary>
        /// <param name="direction"></param>
        /// <returns></returns>
        private NoteType.MoveDirection TranslateToNoteTypeDirection( MoveDirection direction )
        {
            switch( direction )
            {
                case MoveDirection.Up:
                    return NoteType.MoveDirection.Up;
                case MoveDirection.Down:
                    return NoteType.MoveDirection.Down;
                case MoveDirection.Left:
                    return NoteType.MoveDirection.Left;
                case MoveDirection.Right:
                    return NoteType.MoveDirection.Right;
                default:
                    throw new ArgumentOutOfRangeException( "direction", direction, "The Direction is not handled." );
            }
        }

#endregion  //Private methods.

    }
}
